﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace JetBlack.Collections
{
    internal class QueueEmptyException : Exception { }
    internal class QueueClosingException : Exception { }

    internal class BlockingListQueue<T> : IDisposable
    {
        private readonly ListQueue<T> queue;
        private readonly ManualResetEvent readEvent = new ManualResetEvent(false);
        private bool closing;

        public BlockingListQueue()
        {
            queue = new ListQueue<T>();
        }

        public BlockingListQueue(IEnumerable<T> collection)
        {
            queue = new ListQueue<T>(collection);
        }

        public BlockingListQueue(int capacity)
        {
            queue = new ListQueue<T>(capacity);
        }

        public void Enqueue(T item)
        {
            lock (this)
            {
                if (closing)
                    throw new QueueClosingException();

                queue.Enqueue(item);
            }
            readEvent.Set();
        }

        public bool Contains(T item)
        {
            lock (this)
            {
                bool contains = queue.Contains(item);
                return contains;
            }
        }

        public void Remove(T item)
        {
            lock (this)
            {
                queue.Remove(item);
            }
        }

        public void Clear()
        {
            lock (this)
            {
                queue.Clear();
            }

            if (closing)
                return;

            readEvent.Reset();
        }

        public int Count
        {
            get { lock (this) { return queue.Count; } }
        }

        public T Front
        {
            get { lock (this) { return queue.Front; } }
            set { lock (this) { queue.Front = value; } }
        }

        public T Back
        {
            get { lock (this) { return queue.Back; } }
            set { lock (this) { queue.Back = value; } }
        }

        public bool IsEmpty
        {
            get { lock (this) { return queue.IsEmpty; } }
        }

        public T Dequeue()
        {
            readEvent.WaitOne();
            lock (this)
            {
                if (IsEmpty)
                {
                    if (!readEvent.SafeWaitHandle.IsClosed)
                        readEvent.Set(); // Allow the application to terminate on Exit
                    throw new QueueEmptyException();
                }

                T item = default(T);

                try
                {
                    item = queue.Dequeue();
                }
                catch { }

                if (queue.IsEmpty && !closing)
                    readEvent.Reset();

                return item;
            }
        }


        public T[] DequeueAll()
        {
            readEvent.WaitOne();
            lock (this)
            {
                if (IsEmpty)
                {
                    if (!readEvent.SafeWaitHandle.IsClosed)
                        readEvent.Set();

                    throw new QueueEmptyException();
                }

                T[] items = queue.DequeueAll();

                if (!closing)
                    readEvent.Reset();

                return items;
            }
        }

        public void Close()
        {
            lock (this)
            {
                closing = true;
            }
            readEvent.Set();
        }

        public void Dispose()
        {
            readEvent.Close();
        }

        public bool IsClosing
        {
            get
            {
                lock (this)
                {
                    bool is_closing = closing;
                    return is_closing;
                }
            }
        }
    }
}
